/*
 * Copyright (C) 2025 Thomas Akehurst
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.tomakehurst.wiremock.db.utils;

import com.github.tomakehurst.wiremock.db.config.DatabaseMappingsSource;
import com.github.tomakehurst.wiremock.db.mapper.WireMockJournalMapper;
import com.github.tomakehurst.wiremock.db.service.MultiMappingService;
import com.github.tomakehurst.wiremock.db.service.StubMappingService;
import com.github.tomakehurst.wiremock.db.service.impl.MultiMappingServiceImpl;
import com.github.tomakehurst.wiremock.db.service.impl.StubMappingServiceImpl;
import com.github.tomakehurst.wiremock.standalone.MappingsSource;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;

/**
 * MyBatis 工具类：负责 SqlSessionFactory 初始化、Mapper 注册、服务实例创建
 * 严格遵循分层架构，确保业务层服务依赖注入合规
 */
public class MyBatisUtil {
  // 单例 SqlSessionFactory（线程安全，延迟初始化）
  private static volatile SqlSessionFactory sqlSessionFactory;
  // Journal 相关 Mapper 列表（自动注册，避免XML手动配置）
  private static final Class<?>[] JOURNAL_MAPPERS = {WireMockJournalMapper.class};
  // 转换器实例（复用，避免重复创建）
  private static final MultiMappingConverter MULTI_MAPPING_CONVERTER = new MultiMappingConverter();

  /**
   * 私有构造器：禁止外部实例化（工具类设计）
   */
  private MyBatisUtil() {
    throw new AssertionError("Utility class should not be instantiated");
  }

  /**
   * 初始化 SqlSessionFactory（基于 mybatis-config.xml 配置）
   * 线程安全：双重检查锁确保单例唯一性
   */
  private static void initSqlSessionFactory() {
    if (sqlSessionFactory == null) {
      synchronized (MyBatisUtil.class) {
        if (sqlSessionFactory == null) {
          try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
            // 1. 读取基础配置
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            Configuration baseConfig = builder.build(inputStream).getConfiguration();

            // 2. 自动注册 Journal Mapper（避免XML手动配置）
            registerJournalMappers(baseConfig);

            // 3. 构建最终 SqlSessionFactory
            sqlSessionFactory = builder.build(baseConfig);
            System.out.println("MyBatis SqlSessionFactory initialized from config file successfully");
          } catch (IOException e) {
            throw new RuntimeException("Failed to initialize MyBatis from config file: mybatis/mybatis-config.xml", e);
          }
        }
      }
    }
  }

  /**
   * 重载：通过自定义数据源初始化 SqlSessionFactory（动态切换数据源场景）
   * 保留基础配置，仅替换数据源和事务工厂
   */
  public static void initSqlSessionFactory(DataSource dataSource) {
    if (dataSource == null) {
      throw new IllegalArgumentException("DataSource cannot be null");
    }

    if (sqlSessionFactory == null) {
      synchronized (MyBatisUtil.class) {
        if (sqlSessionFactory == null) {
          try (InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml")) {
            // 1. 读取基础配置（如别名、插件等）
            Configuration baseConfig = new SqlSessionFactoryBuilder().build(inputStream).getConfiguration();

            // 2. 替换数据源和事务工厂（保持原环境ID，确保配置兼容）
            Environment newEnv = new Environment(
                    baseConfig.getEnvironment().getId(), // 复用原环境ID（如 "development"）
                    new JdbcTransactionFactory(), // JDBC事务：支持手动提交/回滚
                    dataSource
            );
            baseConfig.setEnvironment(newEnv);

            // 3. 自动注册 Journal Mapper
            registerJournalMappers(baseConfig);

            // 4. 构建 SqlSessionFactory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(baseConfig);
            System.out.println("MyBatis SqlSessionFactory initialized with custom DataSource successfully");
          } catch (IOException e) {
            throw new RuntimeException("Failed to initialize MyBatis with custom DataSource", e);
          }
        }
      }
    }
  }

  /**
   * 自动注册 Journal 相关 Mapper（避免重复注册，确保幂等性）
   */
  private static void registerJournalMappers(Configuration config) {
    for (Class<?> mapperClass : JOURNAL_MAPPERS) {
      if (!config.hasMapper(mapperClass)) { // 检查是否已注册，避免重复加载
        config.addMapper(mapperClass);
        System.out.println("Registered MyBatis Mapper: " + mapperClass.getName());
      }
    }
  }

  /**
   * 获取 SqlSession（默认不自动提交，支持事务手动控制）
   * 场景：需多步操作原子性（如 Stub 保存 + 多存根关联）
   */
  public static SqlSession getSqlSession() {
    initSqlSessionFactory(); // 确保工厂已初始化
    return sqlSessionFactory.openSession(false); // false：手动提交事务
  }

  /**
   * 重载：指定事务自动提交模式（灵活适配不同场景）
   * @param autoCommit true：自动提交（只读查询）；false：手动提交（写操作）
   */
  public static SqlSession getSqlSession(boolean autoCommit) {
    initSqlSessionFactory();
    return sqlSessionFactory.openSession(autoCommit);
  }

  /**
   * 获取数据库版 MappingsSource（核心入口：提供合规的服务实例）
   * 严格遵循依赖注入：通过实现类构建服务，向上暴露接口
   */
  public static MappingsSource getDatabaseMappingsSource() {
    initSqlSessionFactory();
    // 1. 构建多存根服务（注入工厂 + 转换器）
    MultiMappingService multiService = new MultiMappingServiceImpl(
            sqlSessionFactory,
            MULTI_MAPPING_CONVERTER
    );
    // 2. 构建单存根服务（注入工厂 + 转换器 + 多存根服务）
    StubMappingConverter stubConverter = new StubMappingConverter(multiService);
    StubMappingService stubService = new StubMappingServiceImpl(
            sqlSessionFactory,
            stubConverter,
            multiService
    );
    // 3. 构建数据源实例（注入业务层接口，符合依赖倒置）
    return new DatabaseMappingsSource(stubService, multiService);
  }

  /**
   * 重载：通过自定义数据源获取 MappingsSource（动态数据源场景）
   */
  public static MappingsSource getDatabaseMappingsSource(DataSource dataSource) {
    initSqlSessionFactory(dataSource); // 用自定义数据源初始化工厂
    // 与默认实现逻辑一致，确保服务依赖注入合规
    MultiMappingService multiService = new MultiMappingServiceImpl(
            sqlSessionFactory,
            MULTI_MAPPING_CONVERTER
    );
    StubMappingConverter stubConverter = new StubMappingConverter(multiService);
    StubMappingService stubService = new StubMappingServiceImpl(
            sqlSessionFactory,
            stubConverter,
            multiService
    );
    return new DatabaseMappingsSource(stubService, multiService);
  }

  /**
   * 对外提供 SqlSessionFactory（供 Journal 存储等其他组件使用）
   */
  public static SqlSessionFactory getSqlSessionFactory() {
    initSqlSessionFactory();
    return sqlSessionFactory;
  }

  /**
   * 重置 SqlSessionFactory（测试场景：避免单例污染）
   * 注意：生产环境慎用，仅用于单元测试或动态切换数据源后重置
   */
  public static void resetSqlSessionFactory() {
    synchronized (MyBatisUtil.class) {
      sqlSessionFactory = null;
      System.out.println("MyBatis SqlSessionFactory has been reset");
    }
  }
}
    